// 观察者+Store
// 注册监听函数

interface Action {
    type: string,
    payload?: any
}

interface Reducer<T> {
    (state: T, action: Action): T;
}

/**
 * 监听函数
 * 切记监听函数是一个无参函数，即使有参数，在dispatch中调用回调函数时不可将store内部的_state外传，否则将破坏state的只读性
 */
// interface ListenerCallback<T> {
//     (state: T): void;
// }
interface ListenerCallback {
    (): void;
}

/**
 * 退订函数
 */
interface UnsubscribeCallback {
    (): void;
}

export class Store<T> {
    private _state: T;
    // 订阅列表
    private _listeners: Array<ListenerCallback> = new Array<ListenerCallback>();

    constructor(private reducer: Reducer<T>, iniState: T) {
        this._state = iniState;
    }

    getState(): T {
        return this._state;
    }

    /**
     * 订阅函数，返回一个退订函数
     * @param listenerCall 
     */
    subscribe(listenerCall: ListenerCallback): UnsubscribeCallback {
        this._listeners.push(listenerCall);
        return () => {
            // 退订函数的上下文中保留两个变量，一个是_listeners，一个是刚刚订阅的listener
            this._listeners = this._listeners.filter((listener: ListenerCallback) => listener !== listenerCall);
        };
    }

    /**
     * 修改state的值，同时通知每一个订阅者，向他们推送当前的state
     * @param action 
     */
    dispatch(action: Action): void {
        this._state = this.reducer(this._state, action);
        // 切记在调用监听函数时不可将本地的_state传入
        // this._listeners.forEach((listener: ListenerCallback<T>) => listener(this._state));
        this._listeners.forEach((listener: ListenerCallback) => listener());
    }
}

/**
 * 圆
 */
class Circle {
    radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }
}

const reducer: Reducer<Circle> = (circle: Circle, action: Action) => {
    switch (action.type) {
        case 'SCALE_UP':
            return  new Circle(circle.radius * action.payload);
        case 'SCALE_BACK':
            return new Circle(circle.radius / action.payload);
        default:
            return circle;
    }
}

// 圆周率
const PI = Math.PI;

const store: Store<Circle> = new Store<Circle>(reducer, new Circle(10));
store.subscribe(() => {
    const circle = store.getState();
    console.log('area: ', circle.radius ** 2 * PI);
    console.log('perimeter: ', circle.radius * 2 * PI);
});
store.dispatch({type: 'SCALE_UP', payload: 2});
store.dispatch({type: 'SCALE_BACK', payload: 2});